﻿using System;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime;

namespace Logging
{
    /// <summary>
    /// Use this to log any event of any object containing any event. No matter if it is a control or not...
    /// </summary>
    public class CEventLogger2
    {
        private Dictionary<string, Delegate> m_oDelegatesDictionary = new Dictionary<string, Delegate>();

        private static bool bStaticEventDumpIsActive = true;

        /// <summary>
        /// Gets or sets a value indicating whether [event dump is active].
        /// </summary>
        /// <value><c>true</c> if [event dump is active]; otherwise, <c>false</c>.</value>
        public bool EventDumpIsActive
        {
            get { return bStaticEventDumpIsActive; }
            set { bStaticEventDumpIsActive = value; }
        }

        /// <summary>
        /// Gets the events names.
        /// </summary>
        /// <param name="_oObject">The _o object.</param>
        /// <returns></returns>
        public string[] GetEventNames(object _oObject)
        {
            EventInfo[] aEventInfo = _oObject.GetType().GetEvents();
            string[] asEventName = new string[aEventInfo.Length];
            for (int i = 0; i < aEventInfo.GetLength(0); i++)
            {
                asEventName[i] = aEventInfo[i].Name;
            }
            return asEventName;
        }

        /// <summary>
        /// Returns the count of registered events.
        /// </summary>
        /// <value>The registered events count.</value>
        public int RegisteredEventsCount
        {
            get { return this.m_oDelegatesDictionary.Count; }
        }

        /// <summary>
        /// Registers all events. Use this to register really every event in an object
        /// </summary>
        /// <param name="_oObject">The _o object.</param>
        public void RegisterAllEvents(object _oObject)
        {
            string[] asEventNames = GetEventNames(_oObject);
            AttachEventHandler(_oObject, asEventNames);
        }

        /// <summary>
        /// Deregisters all events. Use this to deregister really every event in an object
        /// </summary>
        /// <param name="_oObject">The _o object.</param>
        public void DeRegisterAllEvents(object _oObject)
        {
            string[] asEventNames = GetEventNames(_oObject);
            RemoveEventHandler(_oObject, asEventNames);
        }

        /// <summary>
        /// Gets the event handlers delegates.
        /// </summary>
        /// <value>The event handler.</value>
        public Delegate[] EventHandler
        {
            get
            {
                ICollection<Delegate> iCollection = this.m_oDelegatesDictionary.Values;
                Delegate[] aDelegate = new Delegate[iCollection.Count];

                int nCount = 0;
                foreach (object oDelegate in iCollection)
                {
                    aDelegate[nCount++] = (Delegate)oDelegate;
                }
                return aDelegate;
            }
        }

        /// <summary>
        /// Gets the event handlers names.
        /// </summary>
        /// <value>The event handler names.</value>
        public string[] EventHandlerNames
        {
            get
            {
                ICollection<string> iCollection = this.m_oDelegatesDictionary.Keys;
                string[] asKeys = new string[iCollection.Count];

                int nCount = 0;
                foreach (object oKey in iCollection)
                {
                    asKeys[nCount++] = oKey.ToString();
                }
                return asKeys;
            }
        }

        /// <summary>
        /// Attaches the event handler.
        /// </summary>
        /// <param name="_oSelectedObject">The _o selected object.</param>
        /// <param name="_aEvent">The _a event.</param>
        public void AttachEventHandler(object _oSelectedObject, List<string> _aEvent)
        {
            for (int i = 0; i < _aEvent.Count; i++)
            {
                AttachEventHandler(_oSelectedObject, _aEvent[i]);
            }
        }

        /// <summary>
        /// Attaches the event handler.
        /// </summary>
        /// <param name="_oSelectedObject">The _o selected object.</param>
        /// <param name="_saEvent">The _sa event.</param>
        public void AttachEventHandler(object _oSelectedObject, string[] _saEvent)
        {
            for (int i = 0; i < _saEvent.Length; i++)
            {
                AttachEventHandler(_oSelectedObject, _saEvent[i]);
            }
        }

        /// <summary>
        /// Attaches the event handler.
        /// </summary>
        /// <param name="_oSelectedObject">The _o selected object.</param>
        /// <param name="_sEvent">The _s event.</param>
        public void AttachEventHandler(object _oSelectedObject, string _sEvent)
        {
            EventInfo eventInfo = _oSelectedObject.GetType().GetEvent(_sEvent);
            Type htype = eventInfo.EventHandlerType;
            MethodInfo[] methodInfo = htype.GetMethods();

            ParameterInfo[] parameterInfo = methodInfo[0].GetParameters();

            //this assembles a method at runtime, giving it a name like ClickEventHandler something, containing the matching return type and perfect fitted parameters
            DynamicMethod dynamicMethod = new DynamicMethod(_sEvent + "EventHandler", typeof(void), new Type[] { typeof(object), parameterInfo[1].ParameterType }, GetType());

            //using ilgenerator we can use the mechanism to generate code at runtime.
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

            //EventHandler calls static method EventDump to keep it short and simple.
            MethodInfo methodInfoEventDump = GetType().GetMethod("EventDump");

            //Code generieren, der den Ereignisnamen und zwei Argumente auf den Stack legt und dann die Merthod EventDump aufruft.
            ilGenerator.Emit(OpCodes.Ldstr, _sEvent);
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.EmitCall(OpCodes.Call, methodInfoEventDump, null);
            ilGenerator.Emit(OpCodes.Ret);

            //Create delegate using EventHandler template
            Delegate dynamicDelegate = dynamicMethod.CreateDelegate(htype);

            //Install this eventhandler
            try
            {
                eventInfo.AddEventHandler(_oSelectedObject, dynamicDelegate); //EDIT: try catch für ungültige events z.b. mit ActiveX suche noch nach Lösung ungültige events vorher auszuschließen, s. unterer post.
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }

            //Add this delegate to the dictionary
            if (this.m_oDelegatesDictionary.ContainsKey(_sEvent) != null)
            {
                System.Console.WriteLine("This Event already has been added!");
                return;
            }
            m_oDelegatesDictionary.Add(_sEvent, dynamicDelegate);
        }

        /// <summary>
        /// Removes the event handler and deletes it from the dictionary.
        /// </summary>
        /// <param name="_oSelectedObject">The _o selected object.</param>
        /// <param name="_sEvent">The _s event.</param>
        public void RemoveEventHandler(object _oSelectedObject, string _sEvent)
        {
            EventInfo eventInfo = _oSelectedObject.GetType().GetEvent(_sEvent); //Get the eventhandler from specific object
            eventInfo.RemoveEventHandler(_oSelectedObject, m_oDelegatesDictionary[_sEvent]); //remove it by using the handler of the dictionary
            m_oDelegatesDictionary.Remove(_sEvent);
        }


        public void RemoveEventHandler(object _oSelectedObject, string[] _saEvent)
        {
            for (int i = 0; i < _saEvent.Length; i++)
            {
                RemoveEventHandler(_oSelectedObject, _saEvent[i]);
            }
        }

        public void RemoveEventHandler(object _oSelectedObject, List<string> _aEvent)
        {
            for (int i = 0; i < _aEvent.Count; i++)
            {
                AttachEventHandler(_oSelectedObject, _aEvent[i]);
            }
        }

        /// <summary>
        /// Dumps the event.
        /// </summary>
        /// <param name="_sEvent">The _s event.</param>
        /// <param name="_objectSource">The _object source.</param>
        /// <param name="_eventArgs">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        public static void EventDump(string _sEvent, object _objectSource, EventArgs _eventArgs)
        {
            if (!bStaticEventDumpIsActive) return;

            DateTime dt = DateTime.Now;

            //WriteOutput has to be used to be enable to use this with EventLogging-classes or not
            WriteOutput("==================================");
            WriteOutput(_sEvent + "-Event");
            WriteOutput("==================================");
            WriteOutput("{0}.{1:D3} {2}", dt.Second % 10, dt.Millisecond, _sEvent);

            WriteOutput(_objectSource.ToString());
            //Show all properties in eventargs or inherited class.
            PropertyInfo[] aPropertyInfo = _eventArgs.GetType().GetProperties();
            foreach (PropertyInfo propertyInfo in aPropertyInfo)
            {
                WriteOutput("{0}={1}", propertyInfo.Name, propertyInfo.GetValue(_eventArgs, null));
            }

            //Falls das Eeigernis auf Changed eendet, die neue Eigeneschaft anzeigen
            if (_sEvent.EndsWith("Changed"))
            {
                string sProperty = _sEvent.Substring(0, _sEvent.Length - 3);
                PropertyInfo propertyInfo = _objectSource.GetType().GetProperty(sProperty);
                if (propertyInfo != null)
                {
                    WriteOutput("{0}={1}", propertyInfo.Name, propertyInfo.GetValue(_objectSource, null));
                }

            }
            WriteOutput(""); //Zeile abschließen
        }

        /// <summary>
        /// Writes the output.
        /// </summary>
        /// <param name="_sText">The _s text.</param>
        public static void WriteOutput(string _sText)
        {
            WriteOutput(_sText, true, true);
        }

        /// <summary>
        /// Writes the output.
        /// </summary>
        /// <param name="_sText">The _s text.</param>
        /// <param name="_aObject">The _a object.</param>
        public static void WriteOutput(string _sText, params object[] _aObject)
        {
            Write(string.Format(_sText, _aObject), true);
        }

        /// <summary>
        /// Writes the specified _s text. This has to have a different name, because the second overload of WriteOutput could generate a stack overflow otherwise.
        /// </summary>
        /// <param name="_sText">The _s text.</param>
        /// <param name="_bUseOnlyConsoleOutput">if set to <c>true</c> [_b use only console output].</param>
        public static void Write(string _sText, bool _bUseOnlyConsoleOutput)
        {
            if (_bUseOnlyConsoleOutput)
            {
                System.Console.WriteLine(_sText);
            }
            else
            {
                //                Logger.WriteLine(_sText);
            }
        }
    }
}
